今天我們將實作如何從零開始配置 Redis Client 到 SpringBoot 模組。現在目前使用 JVM 記憶體儲存實現限流功能,接下幾天將整合 Redis 來提升系統的可擴展性和持久性。透過 Redis 的整合,我們可以實現分散式限流,讓多個應用實例共享限流狀態。
首先,我們需要在 pom.xml 中添加 Redis 相關的依賴:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
然後之前我有用 Docker Compose 來啟動我的 MySQL Server,Redis 也可以用同樣的方式做配置,到時就可以直接在容器裡運行 Redis Server,首先設置一下環境變數:
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=redis123456
REDIS_DATABASE=0
REDIS_TIMEOUT=2000
REDIS_POOL_MAX_TOTAL=20
REDIS_POOL_MAX_IDLE=10
REDIS_POOL_MIN_IDLE=5
docker-compose.yaml
version: "3.8"
services:
  mysql:
    # skip...
  redis:
    image: redis:7-alpine
    container_name: core-redis
    ports:
      - "${REDIS_PORT:-6379}:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
  mysql_data:
  redis_data:
實際啟動容器:
docker compose up -d redis
登入看看:
redis-cli -a redis123456
然後 ping 看看可不可以連到 Redis:
127.0.0.1:6379> ping
PONG
大功告成!
application.yaml再來是 SpringBoot 的配置:
spring:
  application:
    name: playground-module
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
	# Redis Configuration
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      database: ${REDIS_DATABASE:0}
      timeout: ${REDIS_TIMEOUT:2000}ms
      lettuce:
        pool:
          max-active: ${REDIS_POOL_MAX_TOTAL:20}
          max-idle: ${REDIS_POOL_MAX_IDLE:10}
          min-idle: ${REDIS_POOL_MIN_IDLE:5}
          max-wait: -1ms
這個 config 類是為 Redis Client 在 SpringBoot 專案內使用方式做定義
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
這是為了簡單測試做的類別,用途是訪問 Redis Server,有點像是用來操作 Redis 的工具類的概念:
@Component
@RequiredArgsConstructor
public class RedisHelper {
    private final RedisTemplate<String, Object> redisTemplate;
    public Long increment(String cacheName, Object... keys) {
        return redisTemplate.opsForValue().increment(cacheName.concat(":").concat(String.valueOf(keys)));
    }
}
接著到 Controller 調用看看,然後實際發個 api call 測試有沒有把資料寫進 Redis,並且返回 Redis 裡的資料:
@GetMapping(value = "/redis")
public ResponseEntity<Long> getRedisCount() {
    return ResponseEntity.ok(redisHelper.increment(RedisKey.RATE_LIMITER.getValue(), RequestContextHelper.getClientIp()));
}
測試成功!調用後確認 Redis 裡資料被寫入了,只是 key 之後還要細調,但這次就只是單純做個類似 PoC 的動作:

最近幾天工作上有點繁忙,文章的質量稍嫌下降,今天也是在最後關頭,趕快在一個多小時內生出這篇文章,壓線維持住當前的紀錄,還好明天就週末了,一定要在週末加把勁,多做多寫一點,加油!